`timescale 1ns / 1ps

module pwconv
#(
    parameter N_IN      = 16,
    parameter N_OUT     = 16,
    parameter N_ICH     = 384,
    parameter N_OCH     = 512,
    parameter BIT_I     = 8,
    parameter BIT_W     = 5,
    parameter BIT_O     = 16,
    parameter ROM_FILE  = "L9_PW.mem",
    parameter ROM_TYPE  = "block",
    parameter ROM_LATENCY = 2,
    parameter RAM_TYPE  = "distributed",
    parameter RAM_MODE  = "read_first",
    parameter RAM_LATENCY = 1
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [ N_IN*BIT_I-1 : 0]    i_data,
    
    output  o_vld,
    input   o_rdy,
    output  [N_OUT*BIT_O-1 : 0]    o_data
);

localparam DATAPATH_LATENCY = 3 + N_IN;
localparam W_IN  =  N_IN * BIT_I;
localparam W_OUT = N_OUT * BIT_O;
localparam W_WT  = N_OUT * N_IN * BIT_W;
localparam D_WT  = N_OCH * N_ICH / (N_OUT * N_IN);

wire  ii_vld;
wire  ii_last;
wire  [W_IN-1 : 0]  ii_data;
wire  wt_vld;
wire  [W_WT-1 : 0]  wt_data;

wire in_rdy;
wire in_vld;
wire enable;

wire dy_vld;
wire dy_last;
wire dy_vld2;
wire ce_acc;
reg  ld_acc;    // load: 0; accumulate: 1

assign in_vld = ii_vld & wt_vld;
assign in_rdy = in_vld & enable;
assign enable = ~o_vld | o_rdy;
assign ce_acc = enable & dy_vld2;

pwc_buffer #(
    .N_IN        ( N_IN         ),
    .N_OUT       ( N_OUT        ),
    .N_ICH       ( N_ICH        ),
    .N_OCH       ( N_OCH        ),
    .BIT         ( BIT_I        ),
    .RAM_TYPE    ( RAM_TYPE     ),
    .RAM_MODE    ( RAM_MODE     ),
    .RAM_LATENCY ( RAM_LATENCY  )
) inst_inbuf
(
    .clk                     ( clk      ),
    .rst                     ( rst      ),

    .i_vld                   ( i_vld    ),
    .i_data                  ( i_data   ),
    .i_rdy                   ( i_rdy    ),

    .o_rdy                   ( in_rdy   ),
    .o_vld                   ( ii_vld   ),
    .o_last                  ( ii_last  ),
    .o_data                  ( ii_data  )
);

ROM_Loader #(
    .WIDTH          (W_WT),
    .DEPTH          (D_WT),
    .MEM_FILE       (ROM_FILE),
    .MEM_TYPE       (ROM_TYPE),
    .MEM_LATENCY    (ROM_LATENCY)
) weight_loader
(
    .clk        (clk),
    .rst        (rst),

    .o_vld      (wt_vld),
    .o_rdy      (in_rdy),
    .o_data     (wt_data)
);

shift_reg_rst #(
    .N          (DATAPATH_LATENCY - 2),
    .WIDTH      (2)
) inst_ctrl_line
(
    .clk        (clk),
    .rst        (rst),
    .clken      (enable),
    .i_data     ({in_vld, ii_last}),
    .o_data     ({dy_vld, dy_last})
);

always @(posedge clk)
begin
    if (rst)
        ld_acc <= 1'b0;
    else
    begin
        if (enable)
        begin
            if (dy_last)
                ld_acc <= 1'b0;
            else
                ld_acc <= ld_acc | dy_vld;
        end
    end
end

shift_reg_rst #(
    .N          (1),
    .WIDTH      (1)
) inst_pce_line
(
    .clk        (clk),
    .rst        (rst),
    .clken      (enable),
    .i_data     (dy_vld),
    .o_data     (dy_vld2)
);

shift_reg_rst #(
    .N          (2),
    .WIDTH      (1)
) inst_ovld_line
(
    .clk        (clk),
    .rst        (rst),
    .clken      (enable),
    .i_data     (dy_last),
    .o_data     (o_vld)
);

genvar i;
generate
    for (i = 0; i < N_OUT / 2; i = i + 1)
    begin
        pwc_dspchain #(
            .N_PE   ( N_IN   ),
            .BIT_I  ( BIT_I  ),
            .BIT_W  ( BIT_W  ),
            .BIT_O  ( BIT_O  ),
            .LSHIFT ( 20     )
        ) inst_dspchain
        (
            .clk                     ( clk        ),
            .ce                      ( enable     ),
            .ce_acc                  ( ce_acc     ),
            .ld_acc                  ( ld_acc     ),
            .i_data                  ( ii_data    ),
            .i_weight                ( wt_data[N_IN*2*BIT_W * i + N_IN*2*BIT_W - 1 : N_IN*2*BIT_W * i]  ),
            .o_data                  (  o_data[     2*BIT_O * i +      2*BIT_O - 1 :      2*BIT_O * i]  )
        );
    end
endgenerate

endmodule
